今天我們來介紹form表單相關內容,前2天講到的資料傳遞都是藉由Controller從資料模型中抓取Data,再將Data丟到View呈現的方式。那如果是想把在View頁面輸入的資訊傳回給Controller,再做後續資料傳遞與處理的話該怎麼做呢?
例如我們想建立一個登入畫面,使用者可以輸入帳號密碼後按登入並顯示登入後畫面,這時候就會需要用到關鍵的HTML表單 (form)了!
因為之前的DemoController已經寫了太多方法,避免混亂我們另外新建一個Controller叫做LogInController,忘記怎麼做的話可以回去看Day3的內容~
建立後Code如下:
    public class LogInController : Controller
    {
        // GET: LogIn
        public ActionResult Index()
        {
            return View();
        }
    }
接著新增View,View的內容會使用HTML的form標籤,在View修改為如下列的Code:
@{
    ViewBag.Title = "Index";
}
<h2>LogIn Page</h2>
<div class="container">
    <form method="post" action="~/login/verify">
        <label>Account</label>
        <br />
        <input type="text" name="account" />
        <br /><br />
        <label>Password</label>
        <br />
        <input type="text" name="password" />
        <br /><br />
        <input type="submit" name="submit" value="Login" class="btn btn-primary" />
    </form>
</div>
完成後執行畫面如下:
簡單說明一下上面範例form標籤的語法,幾個重點如下:
<form>標籤裡面才能傳遞。<form>標籤的method屬性決定用何種HTTP請求方法傳送,主要為post但也有get選項。<form>標籤的action屬性決定送出表單後,表單內容該送去哪個URL。submit按鈕,一個表單內只能有一個submit按鈕。name屬性繫結,才能讓後端對應到元素欄位。另外提一下標籤裡面會看到class屬性,這是用來美化標籤樣式的功能,預設是套用Bootstrap套件的樣式檔,後面會專門開一篇來介紹。
因為我們已經在form裡面給定了method使用post方法,以及action要傳送到~/login/verify 這個URL,代表我們在LoginController內就要有個對應的動作方法叫做Verify,且這個方法是接受HTTP POST的請求。因此我們新增如下Code:
        [HttpPost]
        public ActionResult Verify()
        {
            return View();
        }
注意動作方法的上方一定要標註[HttpPost] ,否則預設都會視為[HttpGet]。
接著說明form的內容如何繫結到動作方法,第一種是使用FormCollection來抓取表單內資料元素,因為資料量不大這邊直接使用ViewBag攜帶,我們將Verify()方法修改Code如下:
        [HttpPost]
        public ActionResult Verify(FormCollection obj)
        {
            ViewBag.Account = obj["account"]; 
            ViewBag.Password = obj["password"];
            return View();
        }
要注意的重點在於FormCollection使用form裡面元素的name屬性來繫結(不區分大小寫),對照下圖:

最後來新增Verify()方法對應的View,Code如下:
@{
    ViewBag.Title = "Verify";
}
<h2>Verify</h2>
<div>
    <ol>
        <li>User: @ViewBag.Account</li> 
        <li>Pwd: @ViewBag.Password</li>
    </ol>
</div>
執行的時候注意不是直接連到~/login/verify 這個URL,因為它是藉由POST方法請求,而直接輸入網址是一種GET方法,我們並沒有建立對應GET的動作方法。我們應該要從原本的~/login/Index 執行,輸入帳號與密碼欄位內容,並按下Submit按鈕提交表單,依照表單的method與action屬性才會轉到~/login/verify並顯示對應的View。
按下Login後畫面如下:
另外一種方式是直接將參數名稱對應form裡面元素name屬性,並代入動作方法,修改Verify()方法Code如下:
        [HttpPost]
        public ActionResult Verify(string account, string password)
        {
            ViewBag.Account = account;
            ViewBag.Password = password;
            return View();
        }
View的部分不用修改,顯示結果會與上面相同。
最後一種方式就是自行建立類別與屬性,來對應form裡面元素name屬性,首先新增一個LoginViewModel 類別於Models資料夾底下,並加入對照form的屬性Account與Password,Code如下:
    public class LoginViewModel
    {
        public string Account { get; set; }
        public string Password { get; set; }
    }
接著修改Verify()方法Code如下:
        [HttpPost]
        public ActionResult Verify(LoginViewModel vm) //using Models
        {
            ViewBag.Account = vm.Account;
            ViewBag.Password = vm.Password;
            return View();
        }
View的部分一樣不用修改,顯示結果會與上面相同。
一開始我們有提到form表單其實可以使用get方法來提交資料,那呈現方式會是如何呢?
首先當然我們要先把View的<form>裡面的method屬性改為get,Code如下:
@{
    ViewBag.Title = "Index";
}
<h2>LogIn Page</h2>
<div class="container">
    <form method="get" action="~/login/verify">
        <label>Account</label>
        <br />
        <input type="text" name="account" />
        <br /><br />
        <label>Password</label>
        <br />
        <input type="text" name="password" />
        <br /><br />
        <input type="submit" name="submit" value="Login" class="btn btn-primary" />
    </form>
</div>
接著在Controller將Verify()動作方法上面的[HttpPost]拿掉或改成[HttpGet],Code如下:
        [HttpGet]
        public ActionResult Verify(LoginViewModel vm)
        {
            ViewBag.Account = vm.Account;
            ViewBag.Password = vm.Password;
            return View();
        }
重新執行Index畫面並登入,畫面如下:

眼睛比較利的人可以發現,當使用GET請求方式提交表單時,表單輸入的資訊會被放到URL的尾端,這與我們在Day3時講到的重點相同。這樣是否會有點問題呢?因為帳號密碼資訊不應該在網址上可以被看到才對呀!?所以透過這個例子可以瞭解,為何form通常會使用POST而不用GET的理由之一了,但原因不僅僅是這樣而已,下面會做個詳細比較整理。
※Day 3內容節錄:
在form表單使用POST的理由可能如下:
1. 安全性:
如上面範例所述,使用GET請求時把資料放在URL上傳遞是一目瞭然的,我們並不希望隱密的內容可以如此被直接看到。而使用POST請求時,傳遞內容會放在請求的message body裡面,這相對安全多了。(但實際上仍可透過查看HTTP封包內容得知)
2. 參數長度限制:
例如我們知道GET請求是將參數放在URL尾端傳送,但根據不同瀏覽器URL限制的長度會不同(ex: IE為2083 bytes、Chrome為8192 bytes),需要傳遞的參數過多可能會造成影響。而POST是將傳遞參數放在請求的message body 中,就沒有長度限制問題。
3. 為了新增或修改資源:
根本上來說,HTTP/1.1協定中定義的POST請求就是向指定資源提交資料,請求伺服器進行處理(例如提交表單或者上傳檔案)。這個請求可能會建立新的資源或修改現有資源,或二者皆有。
例如提交表單的目的是要新增會員資料、修改現有密碼、或者將商品加入購物車清單等等,這些動作會新增或變更資料庫內容,此時就應該使用POST方法;而如果發送表單只為了幫助查尋資源內容,那使用GET就無妨。
※這邊可以延伸出來講個名詞叫做冪等性(idempotency),冪等指的是,假如在不考慮諸如錯誤或者過期等問題的情況下,若干次請求的副作用與單次請求相同或者根本沒有副作用,那麼這些請求方法就能夠被視作「冪等(idempotence)」的。
例如GET請求是「冪等」的,因為它只讀取顯示內容而不會新增或修改到資源,同樣的請求即使不小心送了兩次,也不用擔心對伺服器端造成不同的影響。而POST請求則是「非冪等」的,因為每次提交表單都會對資源造成影響,如果User不小心送了兩次請求,就有可能造成重複提交表單,這也是網頁開發者需要去防範的問題。
GET 與 POST的詳細比較表格如下:
| 請求方式 | GET | POST | 
|---|---|---|
| 資料傳遞方式 | 將參數放在URL尾端,傳遞至Server | 傳遞參數會放在請求的 message body 中 | 
| 參數長度限制 | URL長度限制根據瀏覽器、伺服器會有所不同。 | 長度無限制 | 
| 安全性 | 傳遞的參數會在URL上顯示,較POST不安全 | 較GET安全,但實際上內容可透過HTTP封包看到 | 
| 資料種類 | 只允許 ASCII,非ASCII會進行轉碼 | 無限制 | 
| 重新載入 | 不影響顯示結果 | 會詢問重新提交資料 | 
| 瀏覽器書籤 | 可以加入 | 無法加入 | 
| 冪等性 | 冪等 | 非冪等 | 
今天主要講了form表單傳送資料的幾種方式,以及GET與POST的比較,明天來講和View畫面呈現息息相關的BootStrap套件功能,那就明天見囉~
※小弟不才,在軟體的世界還只是個小菜雞,如果內容有任何謬誤或問題,還請各位大神前輩們多多批評指教~歡迎下方留言討論^^